Skip to content

Add Transparent Reader Support (TRS)#324

Open
sidcha wants to merge 11 commits into
masterfrom
trs_dev
Open

Add Transparent Reader Support (TRS)#324
sidcha wants to merge 11 commits into
masterfrom
trs_dev

Conversation

@sidcha

@sidcha sidcha commented Jul 12, 2026

Copy link
Copy Markdown
Member

TRS tunnels raw smart-card APDUs from the CP, through a PD acting as a transparent pipe, to a card in the reader (OSDP CMD_XWR/REPLY_XRD, capability SMART_CARD_SUPPORT). The control panel drives the card directly, so a compromised reader cannot forge a credential.

The CP runs the session as a library-driven state machine: it negotiates transparent mode, streams the app's APDUs, and tears the session down on osdp_cp_trs_stop(). Apps submit OSDP_CMD_XWR commands and receive card responses as OSDP_EVENT_TRS events.

The PD answers each APDU either synchronously, in the command callback, or deferred: it ACKs "working" and delivers the response on a later poll once a slow card responds. Deferral keeps a shared multi-drop bus non-blocking, so each PD's session makes progress independently.

Gated behind OPT_BUILD_OSDP_TRS (default off); wired into both build systems and covered by a CP<->PD APDU round-trip unit test.

Co-developed-by: Aaron Tulino (Aaronjamt) git@aaronjamt.com

sidcha added 11 commits July 23, 2026 13:04
TRS tunnels raw smart-card APDUs from the CP, through a PD acting as
a transparent pipe, to a card in the reader (OSDP CMD_XWR/REPLY_XRD,
capability SMART_CARD_SUPPORT). The control panel drives the card
directly, so a compromised reader cannot forge a credential.

The CP runs the session as a library-driven state machine: it
negotiates transparent mode, streams the app's APDUs, and tears the
session down on osdp_cp_trs_stop(). Apps submit OSDP_CMD_XWR commands
and receive card responses as OSDP_EVENT_TRS events.

The PD answers each APDU either synchronously, in the command
callback, or deferred: it ACKs "working" and delivers the response on
a later poll once a slow card responds. Deferral keeps a shared
multi-drop bus non-blocking, so each PD's session makes progress
independently.

A Card Present reply is accepted with or without the trailing status
byte -- the spec marks it optional (v2.2 section 7.26.8) and readers
like the HID RPK40 omit it; the status then reports as unspecified.

Gated behind OPT_BUILD_OSDP_TRS (default off); wired into both build
systems and covered by a CP<->PD APDU round-trip unit test.

Fixes: #22
Co-developed-by: Aaron Tulino (Aaronjamt) <git@aaronjamt.com>
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
Table 36 marks the mode-config byte optional: in its absence 0x00 is
used. The PD decoder demanded both mode and config, NAK-ing a 3-byte
Mode-Set that a foreign ACU may legitimately send.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The RPK40 NAKs a CARD_SCAN it does not implement with MSG_CHK, which
the CP's corrupted-frame heuristic answered with up to eight blind
retransmissions before tearing the card session down. Replaying card
traffic can have card-side effects (PIN retry counters, transaction
state), so CMD_XWR is exempt from the resend heuristic entirely; and
since a reader may pick any NAK code it fancies for an unimplemented
card command, every NAK to an app-owned card command is a soft
failure -- one failed command, session intact -- unless the code says
the link itself is broken (SEQ_NUM, SC_UNSUP, SC_COND).

The test runs on a plaintext link, as in the field trace: the mock
channel rewrites the reader's reply into a NAK(MSG_CHK) at the same
sequence number, which is also why the mock gained a no-SCBK device
setup -- rewriting a secured reply would desync the MAC chain, which
a real quirky reader never does.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
A reader left in transparent mode -- failed teardown, CP restart --
keeps answering polls with XRD replies. The CP treated those as
unknown responses and cycled the PD offline for the full retry
penalty, taking a healthy link down over a stale reader mode.

Swallow the reply with a warning instead: it is well-formed, it just
has no session to route to. The payload is dropped, no event reaches
the app, and the link stays up.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The 64-byte APDU carrier could not hold what the headline use case
moves: PIV certificate reads chain GET RESPONSE chunks up to the
short-APDU maximum of 258 bytes (255 data + SW1SW2).

Raise the default to 258 behind an #ifndef so constrained builds can
dial it back down -- osdp_cmd/osdp_event embed a buffer of this size,
so the override is also the memory knob. The usable size is further
bound by the negotiated packet size; a C-APDU that cannot fit it is
now rejected at submit time instead of failing mid-band. Full-size
chunks additionally need -DOSDP_PACKET_BUF_SIZE=512 (default 256).

Chunking inside the library would be wrong here: TRS is a transparent
tunnel and ISO 7816-4 has its own chaining.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The card (emu-card.c, libosdp-free) speaks just enough ISO 7816-4 to
model the headline workflow: SELECT of the PIV AID, GET DATA of a
1.2KB certificate-sized object, and 61xx/GET RESPONSE chaining.

The scenarios drive it through a real CP<->PD session over the mock
channel, modeled on the reader behaviour reported in issue #22: a
full chained certificate read, a session that must ride out a busy
spell, a permanently-busy reader costing one soft-failed command, and
a card leaving the field mid-band with no card-not-present courtesy
from the reader.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The channel-hook frame parsers assumed the leading mark byte from
build options, but its presence also varies by direction and RX mode;
detect it from the frame instead. Also add test-pd-zc.c to the lean
build's test sources -- the zero-copy unit-test binary never linked
there.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
A reader may announce a card in its field as an XRD poll response with
no TRS session open (v2.2 sections 5.11, 7.26.2, 7.26.8). Forward
Mode-0 card-info and Mode-1 card-present reports to the app as
OSDP_EVENT_TRS instead of discarding them; every other out-of-session
XRD is still shrugged off without cycling the PD offline.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
Some readers (HID RPK40) stop reporting ordinary credentials while in
transparent mode and give no smart-card sighting in their default mode,
so neither mode alone can read both credential types. Add an opt-in,
library-managed scan (osdp_cp_trs_scan_enable) that time-slices the
reader: dwell in the default mode, hold transparent mode briefly
watching for a sighting, repeat. A sighting is forwarded as an
OSDP_EVENT_TRS event and transparent mode is held so the band the app
opens in response adopts the reader without renegotiating the mode.

The probe rides the existing TRS session machinery; pd->trs.probe only
suppresses session notifications and band flushing. Ordinary card or
keypad activity restarts the default-mode dwell so a probe never cuts
into an in-progress read. A refused probe raises
OSDP_TRS_SCAN_SUSPENDED and retries with exponential backoff.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
A failed mode-set does not prove the reader stayed in its default mode:
it may have entered transparent mode and had its reply lost or mangled
on the wire, and a reader stuck in mode 01 stops reporting ordinary
card reads. Mode-set(00) is idempotent, so always spend one teardown
round trip instead of assuming there is nothing to undo.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
Scan cadence (silent mode flips, no notification/completion spam),
mode-0 activity deferring probes, disable/flush mid-probe, sighting
hold with band adoption (exactly one mode-set(01) on the wire from
enable to band close), refused-probe backoff with recovery, and an
emulated RPK40 workflow: standard read, smart-card sighting during a
probe, full chained PIV certificate read over the adopted session,
standard read again.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant